#include "JAudioDevice.h"
#include <windows.h>
#include <mmsystem.h>
#include "hw_lib.h"
#include <stdio.h>
#define InputBufferCnt 3
#define OutputBufferCnt 10//20
int StartTime = 0;
typedef struct 
{
	HWAVEIN handle;
	WAVEHDR WaveHead[InputBufferCnt];
	int		UsingHeadIndex;
	int		BufferedBlock;
	int		CompleteBlock;
	char*	Buffer[InputBufferCnt];
	void*	Event;
	void*	Critical;
}InputAudioInst;

typedef struct 
{
	HWAVEOUT handle;
	WAVEHDR WaveHead[OutputBufferCnt];
	int		UsingHeadIndex;
	int		BufferedBlock;
	int		CompleteBlock;
	int		ReadyBlock;
	void*	Event;
	void*	Critical;
}OutputAudioInst;
unsigned int pTime[OutputBufferCnt];
char pBuffer[OutputBufferCnt][1024];
JAudioDevice::JAudioDevice()
{
	InputInst = 0;	
	OutputInst = 0;
	nPlayTime = -1;
	hCritical = hw_init_critical();
}
JAudioDevice::~JAudioDevice()
{
	CloseInput();
	CloseOutput();
	hw_release_critical(hCritical);
}

void CALLBACK waveInProc(HWAVEIN hwi, UINT uMsg, DWORD dwInstance, DWORD dwParam1, DWORD dwParam2 )
{
	if( uMsg == WIM_DATA )
	{
		InputAudioInst* inst = (InputAudioInst*)dwInstance;
		hw_enter_critical( inst->Critical );
		inst->CompleteBlock ++;
//		printf( "input ready %d %d\n" , inst->CompleteBlock , inst->BufferedBlock);
		hw_set_event(inst->Event);	
		hw_leave_critical( inst->Critical );
	}
	else if( uMsg == WOM_DONE )
	{
		OutputAudioInst* inst = (OutputAudioInst*)dwInstance;
		hw_enter_critical( inst->Critical );
		inst->CompleteBlock ++;
//		printf( "output ready %d %d\n" , inst->CompleteBlock , inst->BufferedBlock);
		hw_set_event(inst->Event);	
		hw_leave_critical( inst->Critical );
	}
}

int JAudioDevice::OpenInput( int channel , int samplerate , int samplewidth , int capsize)
{
	int error = 0;
	InputInst = malloc( sizeof(InputAudioInst) );
	InputAudioInst* inst = (InputAudioInst*)InputInst;
	if( InputInst )
		memset( InputInst , 0 , sizeof(InputAudioInst) );
	else
		error = 1;
	if( !error )
	{
		WAVEINCAPS caps;
		if( waveInGetDevCaps( WAVE_MAPPER , &caps , sizeof(WAVEINCAPS) ) != MMSYSERR_NOERROR )
			error = 2;
	}
	if( !error )
	{
		inst->Event = hw_create_event();
		inst->Critical = hw_init_critical();
		if( !inst->Event || !inst->Critical )
			error = 3;
	}
	if( !error )
	{		
		WAVEFORMATEX format;
		format.wFormatTag = WAVE_FORMAT_PCM;
		format.nChannels = channel;
		format.nSamplesPerSec = samplerate;
		format.wBitsPerSample = samplewidth;
		format.nBlockAlign  = 4;
		format.nAvgBytesPerSec = samplerate * samplewidth * channel / 8;
		format.cbSize = 0 ;
		if( (error = waveInOpen( &inst->handle , WAVE_MAPPER , &format , (DWORD)waveInProc , (DWORD)inst , CALLBACK_FUNCTION )) 
			!=  MMSYSERR_NOERROR )
		{
			hw_trace_msg( HW_TRACE_ERROR , "JAudioDevice::OpenInput fail %d\n" , error );
			error = 4;
		}
	}
	if( !error )
	{
		for( int i=0; i<InputBufferCnt; i++ )
		{
			WAVEHDR* head = &inst->WaveHead[i];
			inst->Buffer[i] = (char*)malloc(capsize);
			if( !inst->Buffer[i] )
			{
				error = 5;
				break;
			}
			head->lpData = inst->Buffer[i];
			head->dwBufferLength = capsize;
			head->dwFlags = 0;
			head->dwLoops = 0;
			if( waveInPrepareHeader( inst->handle , head , sizeof(WAVEHDR) ) !=  MMSYSERR_NOERROR )
			{
				error = 6;
				break;
			}
		}
	}
	if( error )
		CloseInput();
	return error;
}
int JAudioDevice::CloseInput()
{
	InputAudioInst* inst = (InputAudioInst*)InputInst;
	if( inst )
	{
		if( inst->handle )
		{
			waveInStop( inst->handle );
			for( int i=0; i<InputBufferCnt; i++ )
			{
				waveInUnprepareHeader( inst->handle , &inst->WaveHead[i] ,  sizeof(WAVEHDR) );
				if( inst->Buffer[i] )
					free( inst->Buffer[i] );
			}
			waveInClose( inst->handle );
			inst->handle = 0;
		}
		if( inst->Event )
			hw_release_event( inst->Event );
		if( inst->Critical )
			hw_release_critical( inst->Critical );
		free( inst );
		InputInst = 0;
	}
	return 0;
}

int JAudioDevice::ClearInput()
{
	int error = 0;
	InputAudioInst* inst = (InputAudioInst*)InputInst;
	if( !inst )
		error = 1;
	if( !error )
	{
		inst->BufferedBlock = inst->CompleteBlock = 0;
	}
	return error;
}

int JAudioDevice::GetInput( char** buffer , int* size )
{
	int error = 0;
	InputAudioInst* inst = (InputAudioInst*)InputInst;
	if( !buffer || !inst )
		error = 1;
	if( !error )
	{
		while( inst->BufferedBlock < InputBufferCnt && !error )
		{
			WAVEHDR* head = &inst->WaveHead[inst->UsingHeadIndex%InputBufferCnt];
			head->dwBytesRecorded = 0;
			if( (error=waveInAddBuffer( inst->handle , head , sizeof(WAVEHDR) )) 
						==  MMSYSERR_NOERROR )
			{
				inst->UsingHeadIndex ++;
				inst->BufferedBlock ++;
//				printf( "add input buffer %d\n" , inst->BufferedBlock );
			}					
			else
				error = 2;
		}
		if( inst->UsingHeadIndex == InputBufferCnt )
			waveInStart( inst->handle );
	}
	if( !error )
	{
		while(1)
		{
			if( !inst->CompleteBlock )
			{
				if( hw_wait_event( inst->Event , 2000 ) != 0 )
				{
					error = 3;
					break;
				}
			}
			{
				int index = (inst->UsingHeadIndex - inst->BufferedBlock)%InputBufferCnt;
				WAVEHDR* head = &inst->WaveHead[ index ];
				if( (head->dwFlags&WHDR_DONE) || (head->dwBytesRecorded!= 0) )
				{
					*buffer = inst->Buffer[index];
					*size = head->dwBytesRecorded;
					hw_enter_critical( inst->Critical );
					inst->BufferedBlock --;
					inst->CompleteBlock --;
					hw_leave_critical( inst->Critical );
					break;
				}
			}
		}
	}
	return error;
}

int JAudioDevice::OpenOutput( int channel , int samplerate , int samplewidth )
{
	int error = 0;
	OutputInst = malloc( sizeof(OutputAudioInst) );
	OutputAudioInst* inst = (OutputAudioInst*)OutputInst;
	if( OutputInst )
		memset( OutputInst , 0 , sizeof(OutputAudioInst) );
	else
		error = 1;
	if( !error )
	{
		WAVEINCAPS caps;
		if( waveInGetDevCaps( WAVE_MAPPER , &caps , sizeof(WAVEINCAPS) ) != MMSYSERR_NOERROR )
			error = 2;
	}
	if( !error )
	{
		inst->Event = hw_create_event();
		inst->Critical = hw_init_critical();
		if( !inst->Event || !inst->Critical )
			error = 3;
	}
	if( !error )
	{		
		WAVEFORMATEX format;
		format.wFormatTag = WAVE_FORMAT_PCM;
		format.nChannels = channel;
		format.nSamplesPerSec = samplerate;
		format.wBitsPerSample = samplewidth;
		format.nBlockAlign  = channel * samplewidth / 8; //4;
		format.nAvgBytesPerSec =  samplerate * samplewidth * channel / 8;
		format.cbSize = 0 ; 
		if( (error=waveOutOpen( &inst->handle , WAVE_MAPPER , &format , (DWORD)waveInProc , (DWORD)inst , CALLBACK_FUNCTION) ) 
			!=  MMSYSERR_NOERROR )
		{
			hw_trace_msg( HW_TRACE_ERROR , "JAudioDevice::OpenOutput fail %d\n" , error );
			error = 4;
		}
//		if(!error)
//			waveOutSetVolume(inst->handle,0xFFFF/2);
	}
	return error;
}

int JAudioDevice::CloseOutput()
{
	OutputAudioInst* inst = (OutputAudioInst*)OutputInst;
	if( inst )
	{
		if( inst->handle )
		{
			while( inst->CompleteBlock != inst->BufferedBlock )
			{
				hw_delay(1);
			}

			for( int i=0; i<OutputBufferCnt; i++ )
			{
				waveOutUnprepareHeader( inst->handle , &inst->WaveHead[i] ,  sizeof(WAVEHDR) );				
			}
			
		//	waveOutClose( inst->handle );
			inst->handle = 0;
		}
		if( inst->Event )
			hw_release_event( inst->Event );
		if( inst->Critical )
			hw_release_critical( inst->Critical );
		free( inst );
		OutputInst = 0;
	}
	return 0;
}

int JAudioDevice::SetOutput( char*buffer , int size )
{
	int error = 0;
	OutputAudioInst* inst = (OutputAudioInst*)OutputInst;
	if( !buffer || !size || !inst )
		error = 1;
	if( !error )
	{
		if( inst->BufferedBlock == OutputBufferCnt )
		{			
			if( !inst->CompleteBlock )
			{
				hw_wait_event( inst->Event , 1000 );
			}
			hw_enter_critical( inst->Critical );
			for( int i=0; i<inst->CompleteBlock; i++ )
			{
				int index = (inst->UsingHeadIndex - inst->ReadyBlock - inst->BufferedBlock)%OutputBufferCnt;
				WAVEHDR* head = &inst->WaveHead[index];
				if( head->dwFlags & WHDR_DONE )
				{
					inst->BufferedBlock --;
					waveOutUnprepareHeader( inst->handle , head , sizeof(WAVEHDR) );
				}
				else
					error = 2;
			}
			inst->CompleteBlock = 0 ;
			hw_leave_critical( inst->Critical );
		}
	}
	if( !error )
	{
		if( inst->BufferedBlock + inst->ReadyBlock < OutputBufferCnt )		
		{			
			int index = inst->UsingHeadIndex%OutputBufferCnt;
			WAVEHDR* head = &inst->WaveHead[index];
			head->lpData = buffer;
			head->dwBufferLength = size;
			head->dwFlags = 0;
			head->dwLoops = 0;
			if( (error=waveOutPrepareHeader( inst->handle , head , sizeof(WAVEHDR) )) ==  MMSYSERR_NOERROR )
			{		
				inst->ReadyBlock ++;
				inst->UsingHeadIndex ++;
			}
			else
				error = 2;
		}
	}
	if( !error )
	{
		while( inst->ReadyBlock > 0 && inst->ReadyBlock + inst->BufferedBlock == OutputBufferCnt )
		{
			int index = (inst->UsingHeadIndex-inst->ReadyBlock)%OutputBufferCnt;
			WAVEHDR* head = &inst->WaveHead[index];
			if( (error=waveOutWrite( inst->handle , head , sizeof(WAVEHDR) )) == MMSYSERR_NOERROR )
			{
				inst->ReadyBlock --;
				inst->BufferedBlock ++;

			}
			else
				error = 4;
		}
	}	
	return error;
}
int JAudioDevice::SetOutput(char *buffer, int size , int time)
{
	int error = 0;
	OutputAudioInst* inst = (OutputAudioInst*)OutputInst;
	if( !buffer || !size || !inst )
		error = 1;
	if( !error )
	{
		if( inst->BufferedBlock == OutputBufferCnt )
		{			
			if( !inst->CompleteBlock )
			{
				hw_wait_event( inst->Event , 1000 );
			}
			hw_enter_critical( inst->Critical );
			for( int i=0; i<inst->CompleteBlock; i++ )
			{
				int index = (inst->UsingHeadIndex - inst->ReadyBlock - inst->BufferedBlock)%OutputBufferCnt;
				WAVEHDR* head = &inst->WaveHead[index];
				if( head->dwFlags & WHDR_DONE )
				{
					inst->BufferedBlock --;
					waveOutUnprepareHeader( inst->handle , head , sizeof(WAVEHDR) );
				}
				else
					error = 2;
			}
			inst->CompleteBlock = 0 ;
			hw_leave_critical( inst->Critical );
		}
	}
	if( !error )
	{
		if( inst->BufferedBlock + inst->ReadyBlock < OutputBufferCnt )		
		{			
			int index = inst->UsingHeadIndex%OutputBufferCnt;
			memcpy(pBuffer[index],buffer,size);
			WAVEHDR* head = &inst->WaveHead[index];
			head->lpData = pBuffer[index];//buffer;
			head->dwBufferLength = size;
			head->dwFlags = 0;
			head->dwLoops = 0;
			pTime[index] = time;
			if( (error=waveOutPrepareHeader( inst->handle , head , sizeof(WAVEHDR) )) ==  MMSYSERR_NOERROR )
			{		
				inst->ReadyBlock = inst->ReadyBlock + 1;//++;
				inst->UsingHeadIndex = inst->UsingHeadIndex + 1;//++;
			}
			else
				error = 2;
		}
	}
	if( !error )
	{
		while( inst->ReadyBlock > 0 && inst->ReadyBlock + inst->BufferedBlock == OutputBufferCnt )
//		if( inst->ReadyBlock > 0 )//&& inst->ReadyBlock + inst->BufferedBlock >= OutputBufferCnt/2 )
//		if( inst->ReadyBlock > 0 && inst->ReadyBlock + inst->BufferedBlock > OutputBufferCnt/2 )
		{
			int index = (inst->UsingHeadIndex-inst->ReadyBlock)%OutputBufferCnt;
			WAVEHDR* head = &inst->WaveHead[index];
			if( (error=waveOutWrite( inst->handle , head , sizeof(WAVEHDR) )) == MMSYSERR_NOERROR )
			{
				inst->ReadyBlock --;
				inst->BufferedBlock ++;
				hw_enter_critical(hCritical);
				nPlayTime = pTime[index];
				hw_leave_critical(hCritical);
				if(StartTime == 0)
					StartTime = GetTickCount();
			
			}
			else
				error = 4;
		}
	}	
	return error;
}
unsigned int JAudioDevice::GetStartTime()
{
	return StartTime;
}
unsigned int JAudioDevice::GetPlayTime()
{
	unsigned int nTemp;
	hw_enter_critical(hCritical);
	nTemp = nPlayTime;
	hw_leave_critical(hCritical);
	return nTemp;
}
void JAudioDevice::SetVolume(int nVolume)
{
	if(((OutputAudioInst*)OutputInst)->handle)
		waveOutSetVolume(((OutputAudioInst*)OutputInst)->handle,0xFFFF*1.0*nVolume/100);
}
